package plugin

import (
	"github.com/emirpasic/gods/maps/linkedhashmap"
	log "github.com/sirupsen/logrus"
	"reflect"
	"sort"
	"strconv"
	"sync"
)

var DefaultExtRegister *PluginRegister = new(PluginRegister)

func Register(ext interface{}) {

	val := reflect.ValueOf(ext)
	if val.Type().Kind() != reflect.Ptr {
		log.Error("type is not ptr kind for ", val.Type().String())
		return
	}

	ind := reflect.Indirect(val)
	typ := ind.Type()
	var plugin Plugin

	if f, ok := typ.FieldByName("Plugin"); ok {
		tag := f.Tag
		code := tag.Get("PluginCode")
		if code != "" {
			plugin = &DefaultPlugin{Code: code}
		}
	}
	//log.Info(typ.String())
	//log.Info(ext.(PluginCode))
	//log.Info(ext.(PluginCode))
	//log.Info(ext.(RuleChecker))

	if id, ok := ext.(Plugin); ok {
		plugin = &DefaultPlugin{Code: id.PluginCode()}
	}
	if typ.Kind() != reflect.Struct {
		panic("ext not struct")
	}
	ability := ""
	if extId, ok := ext.(PluginId); ok {
		ability = extId.Ability()
	}
	if plugin == nil {
		log.Error("PluginCode is nil, ", typ.String())
		return
	}
	order := 0
	if o, ok := ext.(Ordered); ok {
		order = o.Order()
	}
	//typ := reflect.TypeOf(ext)
	num := typ.NumField()
	for i := 0; i < num; i++ {
		f := typ.Field(i)
		if f.Anonymous && f.Type.Kind() == reflect.Interface {
			tag := f.Tag
			if ability == "" {
				ability = tag.Get("ability")
			}
			if ability == "" {
				ability = f.Type.String()
			}
			orderStr := tag.Get("order")
			o, err := strconv.Atoi(orderStr)
			if err != nil {
				order = o
			}

			def := PluginDef{
				Plugin:  plugin,
				Service: ext,
				Ability: ability,
				Order:   order,
			}
			DefaultExtRegister.Register(&def)
			log.Info("Register plugin:", plugin.PluginCode(), ", [", typ.String(), "] for ", def.Ability)
		}
	}
}

func RegisterFunc(bizCode string, ext interface{}) {
	val := reflect.ValueOf(ext)
	ind := reflect.Indirect(val)
	typ := ind.Type()
	if typ.Kind() != reflect.Func {
		panic("ext not function")
	}

	def := PluginDef{
		Plugin:  &DefaultPlugin{Code: bizCode},
		Func:    ext,
		Ability: typ.String(),
	}
	DefaultExtRegister.Register(&def)
}

func Get(pluginId string) ([]*PluginDef, bool) {
	return DefaultExtRegister.Get(pluginId)
}

type PluginRegister struct {
	//plugins map[string][]*PluginDef
	plugins *linkedhashmap.Map
	lock    sync.RWMutex
}

func (e *PluginRegister) Register(def *PluginDef) {

	e.lock.Lock()
	defer e.lock.Unlock()
	if e.plugins == nil {
		e.plugins = linkedhashmap.New()
		//e.plugins = make(map[string][]*PluginDef)
	}
	defs, ok := e.Get(def.PluginCode())
	if !ok {
		defs = make(PluginDefs, 0)
	}
	defs = append(defs, def)

	//e.plugins[def.PluginCode()] = append(defs, def)
	sort.Sort(defs)
	e.plugins.Put(def.PluginCode(), defs)
}

func (e *PluginRegister) Get(pluginCode string) (PluginDefs, bool) {
	//defs, ok := e.plugins[pluginCode]
	defs, ok := e.plugins.Get(pluginCode)
	if defs == nil || !ok {
		return nil, false
	}
	return defs.(PluginDefs), ok
}
